package com.wmx.thymeleafapp.utils;

import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.security.SecureRandom;
import java.security.Security;
import java.util.Base64;

/**
 * 国密SM4分组密码算法工具类（对称加密）,用于替代DES/AES等国际算法
 * <pre>
 *     SM4为无线局域网标准的分组加密算法，对称加密，用于替代DES/AES等国际算法，于2012年3月21日发布，
 *     SM4算法与 AES算法具有相同的密钥长度和分组长度，均为128位，故对消息进行加解密时，若消息长度过长，需要进行分组，要消息长度不足，则要进行填充。
 *     加密算法与密钥扩展算法都采用32轮非线性迭代结构，解密算法与加密算法的结构相同，只是轮密钥的使用顺序相反，解密轮密钥是加密轮密钥的逆序
 * </pre>
 *
 * @author wangMaoXiong
 * @version 1.0
 * @date 2024/4/14 8:03
 */
public class Sm4Util {
    private static final String ALGORITHM = "SM4";
    private static final String ALGORITHM_ECB_PKCS5PADDING = "SM4/ECB/PKCS5Padding";
    private static final String CHARSET_UTF8 = "utf-8";
    /**
     * SM4算法目前只支持128位（即密钥16字节）
     */
    private static final int DEFAULT_KEY_SIZE = 128;
    private static final Logger log = LoggerFactory.getLogger(Sm4Util.class);

    static {
        // 防止内存中出现多次BouncyCastleProvider的实例
        if (null == Security.getProvider(BouncyCastleProvider.PROVIDER_NAME)) {
            Security.addProvider(new BouncyCastleProvider());
        }
    }

    /**
     * 生成密钥文本
     *
     * @param keySrc — 用于生成密钥文本的随机值，值一致，生成的密钥文本就一致。
     * @return :密钥文本，用于后续进行加解密
     */
    public static String generateKey(String keySrc) throws Exception {
        KeyGenerator keyGen = KeyGenerator.getInstance(ALGORITHM, BouncyCastleProvider.PROVIDER_NAME);
        SecureRandom random = new SecureRandom(keySrc.getBytes(CHARSET_UTF8));
        keyGen.init(DEFAULT_KEY_SIZE, random);
        SecretKey secretKey = keyGen.generateKey();
        String secretKeyText = Base64.getEncoder().encodeToString(secretKey.getEncoded());
        return secretKeyText;
    }

    /**
     * Sm4 加密
     *
     * @param data          ：被加密的数据
     * @param secretKeyText ：密钥文本
     * @return ：加密好的密文(base64)
     * @throws Exception
     */
    public static String encrypt(String data, String secretKeyText) throws Exception {
        SecretKey secretKey = strKeyToSecretKey(secretKeyText);
        byte[] dataBytes = data.getBytes(CHARSET_UTF8);
        byte[] encryptBytes = encryptMode(secretKey, dataBytes, Cipher.ENCRYPT_MODE);
        return Base64.getEncoder().encodeToString(encryptBytes);
    }

    /**
     * 解密
     *
     * @param cipherText    ：密文(base64)
     * @param secretKeyText :密钥文本
     * @return ：明文
     */
    public static String decrypt(String cipherText, String secretKeyText) throws Exception {
        SecretKey secretKey = strKeyToSecretKey(secretKeyText);
        byte[] cipherTextBytes = Base64.getDecoder().decode(cipherText);
        byte[] decryptBytes = encryptMode(secretKey, cipherTextBytes, Cipher.DECRYPT_MODE);
        return new String(decryptBytes, CHARSET_UTF8);
    }

    private static SecretKey strKeyToSecretKey(String strKey) {
        byte[] bytes = Base64.getDecoder().decode(strKey);
        SecretKeySpec secretKey = new SecretKeySpec(bytes, ALGORITHM);
        return secretKey;
    }

    private static byte[] encryptMode(SecretKey desKey, byte[] src, int mode) throws Exception {
        // 加密
        Cipher cipher = Cipher.getInstance(ALGORITHM_ECB_PKCS5PADDING, BouncyCastleProvider.PROVIDER_NAME);
        cipher.init(mode, desKey);
        byte[] enc = cipher.doFinal(src);
        return enc;
    }

    public static void main(String[] args) throws Exception {
        String data = "国密算法：即我国自主研制的算法，摆脱对国外的技术过度依赖，国密即国家密码局认定的国产密码算法。以下是具体的对应关系：对称算法：AES、DES、SM4；非对称算法：RSA、SM2；摘要/杂凑算法：MD5、SHA-I、SM3";
        String keySrc = "c8e514d8-71fc-40f6-9069-19b6a53f68ee";

        String secretKeyText = Sm4Util.generateKey(keySrc);
        // 密钥:/064css82SOrw1HfCCoAJQ==
        System.err.println("密钥:" + secretKeyText);

        String cipherText = Sm4Util.encrypt(data, secretKeyText);
        System.out.println("密文:" + cipherText);

        String deStr = Sm4Util.decrypt(cipherText, secretKeyText);
        System.out.println("明文:" + deStr);
    }

}